package YAPserver.logic;

import java.util.ArrayList;
import java.util.HashMap;

import YAPserver.behaviours.BounceBehaviour;
import YAPserver.gameObjects.GameBall;
import YAPserver.gameObjects.Line;
import YAPserver.gameObjects.Mod;
import YAPserver.gameObjects.Point;
import YAPserver.gameObjects.Slider;
import YAPserver.gameObjects.GameWall;
import YAPserver.mods.ModBehaviour;

/**
 * In this class are all possible getters and setters used for modifying gameplay.
 * All mods can modify the game using this class.
 * This class can also be used to test the game logic. i.e. cheats
 * @author Daniel
 *
 */

public class GameModifier{
	
	private GamePlay m_game;
	private HashMap<String, Integer> activations= new HashMap<String, Integer>();
	private Output m_out;
	
	/**
	 * Constructs a GameModifier with a GamePlay and an Output.
	 * The Output is only used for ModRotate.
	 * @param game gamePlay to be set.
	 * @param out output to be set.
	 */
	public GameModifier(GamePlay game, Output out){
		m_game = game;
		m_out = out;
	}
	
	/**
	 * Constructs a GameModifier with a GamePlay .
	 * @param game gamePlay to be set.
	 */
	public GameModifier(GamePlay game){
		m_game = game;
	}
	
	/**
	 * Adds a ball to the Game.
	 * @param ball gameBall to be added
	 */
	public void addBall(GameBall ball){
		m_game.getBalls().add(ball);
	}
	
	/**
	 * Creates and adds a ball to the Game.
	 * @param middlePoint - Point where the Ball will be constructed
	 * @param angle double angle gives the ball its direction
	 * @param speed double speed at which the ball will travel
	 */
	public void addBall(Point middlePoint, double angle, double speed){
		m_game.getBalls().add(new GameBall(middlePoint, angle, speed,
				((AbstractModel) m_game.getGameModel()).defaultBallRadius));
	}
	
	/**
	 * Creates and adds a ball to the Game.
	 * @param middlePoint - Point where the Ball will be constructed
	 * @param angle double angle gives the ball its direction
	 * @param speed double speed at which the ball will travel
	 * @param radius double radius of the ball
	 */
	public void addBall(Point middlePoint, double angle, double speed, double radius){
		m_game.getBalls().add(new GameBall(middlePoint, angle, speed, radius));
	}
	
	/**
	 * Removes the i'th ball from the ArrayList<GameBall> balls.
	 * @param i i'th ball to be removed
	 */
	public void removeBall(int i){
		m_game.getBalls().remove(i);
	}
	
	/**
	 * Gets the i'th ball.
	 * @param i i'th ball to return
	 * @return i'th ball
	 */
	public GameBall getBalls(int i) {
		return m_game.getBall(i);
	}
	
	/**
	 * Sets a ball's middle point
	 * @param i i'th ball to be modified
	 * @param point point to where the ball will be set
	 */
	public void setBallMiddlePoint(int i, Point point){
		m_game.getBall(i).setMiddlePoint(point);
	}
	
	/**
	 * Sets a ball's radius
	 * @param i i'th ball to be modified
	 * @param radius the new radius of the ball
	 */
	public void setBallRadius(int i, double radius){
		m_game.getBall(i).setRadius(radius);
	}
	
	/**
	 * Sets a ball's speed
	 * @param i i'th ball to be modified
	 * @param speed the new speed of the ball
	 */
	public void setBallSpeed(int i, double speed){
		m_game.getBall(i).setSpeed(speed);
	}
	
	/**
	 * Sets the speed a ball has when it is constructed.
	 * @param speed new default speed
	 */
	public void setBallDefaultSpeed(double speed){
		((AbstractModel) m_game.getGameModel()).defaultBallSpeed = speed;
	}
	
	/**
	 * Sets the radius a ball has when it is constructed.
	 * @param radius new radius
	 */
	public void setBallDefaultRadius(double radius){
		((AbstractModel) m_game.getGameModel()).defaultBallRadius = radius;
	}
	
	/**
	 * Sets a ball's angle.
	 * @param i i'th ball's angle to be set
	 * @param angle new angle
	 */
	public void setBallAngle(int i, double angle){
		m_game.getBall(i).setAngle(angle);
	}
	
	/**
	 * Sets all ball's radiuses to the specified radius.
	 * @param radius new radius of all balls
	 */
	public void setAllBallRadius(double radius){
		for(int i=0; i<m_game.getBalls().size(); i++){
			setBallRadius(i, radius);
		}
	}
	
	/**
	 * Sets all ball's speeds to the specified speed.
	 * @param speed new speed of all balls
	 */
	public void setAllBallSpeed(double speed){
		for(int i=0; i<m_game.getBalls().size(); i++){
			setBallSpeed(i, speed);
		}
	}
	
	/**
	 * Activates a mod for a player specified by his gameId. 
	 * One can create a new Mod and activate directly without having to add it to the movingMods list.
	 * @param id a player's id
	 * @param mod mod to be activated
	 */
	public void activateMod(int id, Mod mod){
		mod.activate(m_game.getPlayer(id));
	}
	
	/**
	 * Activates a mod for a player specified by his gameId. Here it is only necessary to specify what behaviour the mod should have.
	 * This behaviour is then activated and the player feels the full consequences.
	 * @param id a player's id
	 * @param m ModBehaviour to be activated
	 */
	public void activateMod(int id, ModBehaviour m){
		Mod mod = new Mod(new Point(0, 0), 0, 0, 0, this, m);
		mod.activate(m_game.getPlayer(id));
	}
	
	/**
	 * Activates a mod for the player with the smallest gameId. Here it is only necessary to specify what behaviour the mod should have.
	 * This behaviour is then activated and the player feels the full consequences.
	 * @param m ModBehaviour to be activated
	 */
	public void activateMod(ModBehaviour m){
		for(int id: m_game.getPlayers().keySet()){
			activateMod(id, m);
			break;
		}
	}
	
	/**
	 * Calls the deactivate method of the modBehaviour for a player.
	 * @param id player's id
	 * @param mod mod to be deactivated
	 */
	public void deactivateMod(int id, Mod mod){
		mod.deactivate(m_game.getPlayer(id));
	}
	
	/**
	 * Adds a mod to the modsMoving list in this gamePlay. Any mod added to that list will be drawn
	 * in the GUI and will act like any other mod.
	 * @param mod mod to be added to the modsMoving list
	 */
	public void addModMoving(Mod mod){
		m_game.getModsMoving().add(mod);
	}
	
	/**
	 * Adds a mod to the modsActive list in this gamePlay. Note that if the mod hasn't been activated it won't do anything.
	 * Also if the mod's timer hasn't been set to true then the mod will can only leave this list through {@link removeModActive()}
	 * @param mod mod to be added
	 * @see Mod
	 */
	public void addModActive(Mod mod) {
		m_game.getModsActive().add(mod);
	}
	
	/**
	 * Removes a mod from the modsActive list in this gamePlay. This method is typically called by a timed ModBehaviour when it deactivates.
	 * @param mod mod to be removed
	 */
	public void removeModActive(Mod mod){
		m_game.getModsActive().remove(mod);
	}
	
	/**
	 * Removes a mod from the modsMoving list in this gamePlay. This method is typically called by a ModBehaviour when its activate method is called.
	 * @param mod mod to be removed
	 */
	public void removeModMoving(Mod mod){
		m_game.getModsMoving().remove(mod);
	}
	
	/**
	 * Returns the i'th mod in the modsMoving list of this gamePlay.
	 * @param i i'th mod
	 * @return i'th mod in the modsMoving list
	 */
	public Mod getMovingMod(int i){
		return m_game.getModMoving(i);
	}
	
	/**
	 * Returns the i'th mod in the modsActive list of this gamePlay.
	 * @param i i'th mod
	 * @return i'th mod in the modsActive list
	 */
	public Mod getActiveMod(int i){
		return m_game.getModActive(i);
	}
	
	/**
	 * Moves the i'th Mod to a different position be setting its middle point.
	 * @param i i'th mod
	 * @param point point to which the mod will be moved
	 */
	public void setModMiddlePoint(int i, Point point){
		m_game.getModMoving(i).setMiddlePoint(point);
	}
	
	/**
	 * Changes the i'th mod's speed
	 * @param i i'th mod
	 * @param speed double speed to which the mod will be set
	 */
	public void setModSpeed(int i, double speed){
		m_game.getModMoving(i).setSpeed(speed);
	}
	
	/**
	 * Sets the speed a mod has when it is constructed.
	 * @param speed new default speed
	 */
	public void setModDefaultSpeed(double speed){
		((AbstractModel) m_game.getGameModel()).defaultModSpeed = speed;
	}
	
	/**
	 * Changes the i'th mods angle
	 * @param i i'th mod
	 * @param angle the new angle
	 */
	public void setModAngle(int i, double angle){
		m_game.getModMoving(i).setAngle(angle);
	}
	/**
	 * Sets all mod's speeds to the specified speed.
	 * @param speed new speed of all balls
	 */
	public void setAllModSpeed(double speed) {
		for(int i=0; i<m_game.getModsMoving().size(); i++){
			setModSpeed(i, speed);
		}
	}
	/*
	 * should write some more methods for modifying mods
	 */
	/**
	 * Returns the number of lives a specified player has.
	 * @param id a player's id
	 * @return the number of lives a player has
	 */
	public int getPlayerLives(int id){
		return m_game.getPlayer(id).getLives();
	}
//	public void setPlayerWallBehaviour(int id, CollideBehaviour b){
//		m_game.getPlayer(id).setBallBehaviour(b);
//	}
//
//	public void setPlayerWallBehaviour(Player player, CollideBehaviour b) {
//		player.setBallBehaviour(b);
//	}
//
//	public void setPlayerSliderBehaviour(int id, CollideBehaviour b){
//		m_game.getPlayer(id).setBallBehaviour(b);
//	}
	/*
	 * might write some more methods modifying players
	 */

	
	/**
	 * Changes the number of lives a specified player has.
	 * @param id a player's id
	 * @param lives number of lives the player will have
	 */
	public void setPlayerLives(int id, int lives){
		m_game.getPlayer(id).setLives(lives);
	}
	
//	public void setAllPlayerBallBehaviour(CollideBehaviour b){
//		for(int id: m_game.getPlayers().keySet()){
//			m_game.getPlayer(id).setBallBehaviour(b);
//		}
//	}
	/**
	 * Changes the number of lives all players in the game have.
	 * @param lives number of lives all players will have
	 */
	public void setAllPlayerLives(int lives){
		for(int id: m_game.getPlayers().keySet()){
			setPlayerLives(id, lives);
		}
	}
	
	/**
	 * Returns the i'th wall
	 * @param i i'th wall
	 * @return the i'th wall
	 */
	public GameWall getWall(int i){
		return m_game.getWall(i);
	}
	
	/**
	 * Sets how a ballAbsract will bounce off this wall.
	 * @param i i'th wall
	 * @param b new bounceBehaviour of this wall
	 */
	public void setWallBehaviour(int i, BounceBehaviour b){
		m_game.getWall(i).setBounceBehaviour(b);
	}
	
	/**
	 * Sets how a ballAbstract will bounce off any wall.
	 * @param b new bounceBehaviour for all walls
	 */
	public void setAllWallBounce(BounceBehaviour b){
		for(int i=0; i<m_game.getWalls().size(); i++){
			m_game.getWall(i).setBounceBehaviour(b);
		}
	}
	
	/**
	 * Returns the i'th slider in gamePlay.
	 * @param i i'th slider
	 * @return the i'th slider
	 */
	public Slider getSlider(int i){
		return m_game.getSlider(i);
	}
	
	/**
	 * Sets the i'th sliders position.
	 * @param i i'th slider
	 * @param point point to which the slider will be set
	 */
	public void setSliderMiddlePoint(int i, Point point) {
		m_game.getSlider(i).setMiddlePoint(point);	
	}
	
//	public void setSliderMiddlePoint(Slider slider, Point point){
//		slider.setMiddlePoint(point);
//	}
	
	/**
	 * Changes the slider's length by a factor. If the factor is 2.5 then the slider's length will become 2.5 times longer.
	 * @param i i'th slider
	 * @param factor a double of how much the slider's length shall change
	 */
	public void setSliderLength(int i, double factor) {
		double length = factor*m_game.getSlider(i).getLine().getLength();
		m_game.getSlider(i).setLength(length);
	}
	
	/**
	 * Changes the slider's length by a factor. If the factor is 2.5 then the slider's length will become 2.5 times longer.
	 * @param slider slider whose length is to be changed
	 * @param factor a double of how much the slider's length shall change
	 */
	public void setSliderLength(Slider slider, double factor){
		double length = factor*slider.getLine().getLength();
		slider.setLength(length);
	}
	
	/**
	 * Changes a slider's speed.
	 * @param slider slider's speed to be changed
	 * @param speed a double to which the slider's speed will be set
	 */
	public void setSliderSpeed(Slider slider, double speed){
		slider.setSpeed(speed);
	}
	
	/**
	 * Sets how a ballAbsract will bounce off this slider.
	 * @param i i'th slider
	 * @param b the new bounceBehaviour
	 */
	public void setSliderBehaviour(int i, BounceBehaviour b){
		m_game.getSlider(i).setBounceBehaviour(b);
	}
	
	/**
	 * Sets how a ballAbsract will bounce off this slider.
	 * @param slider slider whose bounceBehaviour will be changed
	 * @param b the new bounceBehaviour
	 */
	public void setSliderBehaviour(Slider slider, BounceBehaviour b){
		slider.setBounceBehaviour(b);
	}
	
	/**
	 * Sets how a ballAbstract will bounce off any slider.
	 * @param b the new bounceBehaviour
	 */
	public void setAllSliderBehaviour(BounceBehaviour b){
		for(int i=0; i<m_game.getSliders().size(); i++){
			setSliderBehaviour(i, b);
		}
	}
	
	/**
	 * Changes every sliders' length by a factor. If the factor is 2.5 then the slider's length will become 2.5 times longer.
	 * @param factor
	 */
	public void setAllSliderLength(double factor){
		for(int i=0; i<m_game.getSliders().size(); i++){
			setSliderLength(i, factor);
		}
	}
	//TODO: write more setAll methods
	/**
	 * @return returns this gamePlay
	 */
	public GamePlay getGamePlay(){
		return this.m_game;
	}
	
	/**
	 * Checks for collisions between a list of balls with walls and sliders.
	 * @param balls an arrayList of balls who need collisions testing
	 */
	@SuppressWarnings("unchecked")
	public void checkCollisions(@SuppressWarnings("rawtypes") ArrayList balls) {
		if(balls.get(0) instanceof GameBall){
			m_game.getGameModel().checkCollisions(
					balls, new ArrayList<Mod>(),
					m_game.getWalls(),
					m_game.getSliders());
		}
		if(balls.get(0) instanceof Mod){
			m_game.getGameModel().checkCollisions(
					new ArrayList<GameBall>(), balls,
					m_game.getWalls(),
					m_game.getSliders());
		}
	}
	
	/**
	 * Checks for any collisions in the playing field.
	 */
	public void checkCollisions(){
		m_game.getGameModel().checkCollisions(
				m_game.getBalls(),
				m_game.getModsMoving(),
				m_game.getWalls(),
				m_game.getSliders());
	}
	
	/**
	 * Checks for any collisions between gameBalls and other gameObjects in the playing field.
	 */
	public void checkBallCollisions(){
		m_game.getGameModel().checkCollisions(
				m_game.getBalls(),
				new ArrayList<Mod>(),
				m_game.getWalls(),
				m_game.getSliders());
	}
	
	/**
	 * Checks for any collisions between mods and other gameObjects in the playing field.
	 */
	public void checkModCollisions(){
		m_game.getGameModel().checkCollisions(
				new ArrayList<GameBall>(),
				m_game.getModsMoving(),
				m_game.getWalls(),
				m_game.getSliders());
	}
	
	/**
	 * Checks whether a ball has collided with two or more walls. This typically happens when a modHugeBall is activated in a corner.
	 */
	public void checkDoubleCollisions(){
		int count = 0;
		for(GameBall ball: m_game.getBalls()){
			for(GameWall wall: m_game.getWalls()){
				if(ball.hasCollided(wall)){
					count++;
				}
			}
			if(count >= 2){
				Line line = new Line(ball.getMiddlePoint(), new Point(300, 300));
				double angle = line.getAngle();
				ball.setAngle(angle);
			}
			count = 0;
		}
	}

	/**
	 * 
	 * @return the number of balls in the field.
	 */
	public int getNumberOfBalls() {
		return m_game.getBalls().size();
	}
	
	/**
	 * Adds one to the number of active mods with this modBehaviour.
	 * @param name the string name of the modBehaviour
	 */
	public void addActivation(String name){
		if(activations.containsKey(name)){
			int numOfActives = activations.get(name);
			numOfActives++;
			activations.put(name, numOfActives);
		}else{
			int numOfActives = 1;
			activations.put(name, numOfActives);
		}
	}
	
	/**
	 * Keeps track of which mod has been activated by which player
	 * @param name the string name of the modBehaviour
	 * @param id the player's id
	 */
	public void setActivation(String name, int id){
		activations.put(name, id);
	}
	
	/**
	 * Subtracts one from the number of active mods with this modBehaviour.
	 * @param name the string name of the modBehaviour
	 */
	public void subtractActivation(String name){
		if(activations.containsKey(name)){
			int numOfActives = activations.get(name);
			numOfActives--;
			activations.put(name, numOfActives);
		}else{
			int numOfActives = 0;
			activations.put(name, numOfActives);
		}
	}
	
	/**
	 * Removes the mod from the activation's list so there aren't any ghost links.
	 * @param name the string name of the modBehaviour
	 */
	public void removeActivation(String name){
		activations.remove(name);
	}
	
	/**
	 * Retunrs the number of active mods with this modBehaviour
	 * @param name the string name of the modBehaviour
	 * @return the number of active mods with this modBehaviour
	 */
	public int getActivations(String name){
		if(activations.containsKey(name)){
			return activations.get(name);
		}else{
			return -1;
		}
	}

	/**
	 * Sends the rotate message to all clients connected to this game. This method is called by {@link YAPserver.mods.ModRotate}
	 * @param s the string message containing the information that will be sent to the client
	 */
	public void send(String s) {
		m_out.sendRotation(s);
	}
}
